/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */
package czStvDpds.graphics3D;

import czStvDpds.graphics3D.geometry.Object3D;
import czStvDpds.graphics3D.geometry.Texture;
import java.nio.ByteBuffer;
import java.nio.FloatBuffer;
import java.nio.IntBuffer;
import java.util.LinkedList;
import org.newdawn.slick.UnicodeFont;

/**
 *
 * @author divad
 */
public class Synchronizer implements GLSynchronizer {

    private static Synchronizer synchronizer;
    private LinkedList<Texture> texturesToGenerate, changedTextures;
    private LinkedList<Object3D> vbosToGenerate;
    private LinkedList<ChangedTexture> texturesToChange;
    private LinkedList<ChangedVBO> vbosToChange;
    private LinkedList<UnicodeFont> fontsToLoad;

    public static Synchronizer getSynchronizer() {
        if (synchronizer == null) {
            synchronizer = new Synchronizer();
        }
        return synchronizer;
    }

    private Synchronizer() {
        this.texturesToGenerate = new LinkedList();
        this.changedTextures = new LinkedList();
        this.vbosToGenerate = new LinkedList();
        this.texturesToChange = new LinkedList();
        this.vbosToChange = new LinkedList();
        this.fontsToLoad = new LinkedList();
    }

    /**
     * Donutí GL skutečně vygenerovat textury
     */
    public void doGenerateTextures() {
        while (this.texturesToGenerate.size() > 0) {
            Texture t;
            synchronized (this.texturesToGenerate) {
                t = this.texturesToGenerate.removeLast();
            }
            if (t != null) {
                TextureCreator.generateTexture(t);
            }
        }
    }

    public void doChangeTextures() {
        while (this.texturesToChange.size() > 0) {
            ChangedTexture ct;
            synchronized (this.texturesToChange) {
                ct = (this.texturesToChange.removeLast());
            }
            if (ct != null) {
                TextureCreator.changeTexture(ct.getTexture(), ct.x, ct.y, ct.z, ct.width, ct.height, ct.depth, ct.changedData);
            }
        }
    }

    public void doChangedTextures() {
        while (this.texturesToChange.size() > 0) {
            Texture tex;
            synchronized (this.texturesToChange) {
                tex = (this.changedTextures.removeLast());
            }
            if (tex != null) {
                tex.setInGL();
            }
        }
    }

    /**
     * Donutí GL skutečně vygenerovat VBO
     */
    public void doGenerateVBOs() {
        while (this.vbosToGenerate.size() > 0) {
            Object3D vbo;
            synchronized (this.vbosToGenerate) {
                vbo = this.vbosToGenerate.removeLast();
            }
            if (vbo != null) {
                vbo.toVBO();
            }
        }
    }

    public void doChangeVBOs() {
        while (this.vbosToChange.size() > 0) {
            ChangedVBO cvbo;
            synchronized (this.vbosToChange) {
                cvbo = this.vbosToChange.removeLast();
            }
            if (cvbo != null) {
                if (cvbo.data != null) {
                    cvbo.getObject().changeVBOData(cvbo.offset, cvbo.data);
                } else {
                    cvbo.getObject().changeVBOIndices(cvbo.offset, cvbo.indices);
                }
            }
        }
    }

    public void doGenerateGlyphs() {
        while (this.fontsToLoad.size() > 0) {
            try {
                synchronized (this.fontsToLoad) {
                    this.fontsToLoad.removeLast().loadGlyphs();
                }
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }

    @Override
    public void changeTextureData(Texture t, ByteBuffer data, int offsetX, int offsetY, int offsetZ, int width, int height, int depth) {
        synchronized (this.texturesToChange) {
            this.texturesToChange.addFirst(new ChangedTexture(t, offsetX, offsetY, offsetZ, width, height, depth, data));
        }
    }

    @Override
    public void changedTextureProperties(Texture t) {
        synchronized (this.changedTextures) {
            this.changedTextures.addFirst(t);
        }
    }

    @Override
    public void changeVBOData(Object3D object, FloatBuffer data, int offset) {
        synchronized (this.vbosToChange) {
            this.vbosToChange.addFirst(new ChangedVBO(object, data, offset));
        }
    }

    @Override
    public void changeVBOIndices(Object3D object, IntBuffer indices, int offset) {
        synchronized (this.vbosToChange) {
            this.vbosToChange.addFirst(new ChangedVBO(object, indices, offset));
        }
    }

    @Override
    public void generateTexture(Texture texture) {
        synchronized (this.texturesToGenerate) {
            this.texturesToGenerate.addFirst(texture);
        }
    }

    @Override
    public void generateVBO(Object3D vbo) {
        synchronized (this.vbosToGenerate) {
            this.vbosToGenerate.addFirst(vbo);
        }
    }

    @Override
    public void generateGlyphs(UnicodeFont font) {
        synchronized (this.fontsToLoad) {
            this.fontsToLoad.addFirst(font);
        }
    }

    private class ChangedTexture {

        Texture texture;
        int x, y, z, width, height, depth;
        ByteBuffer changedData;

        public ChangedTexture(Texture texture, int x, int y, int z, int width, int height, int depth, ByteBuffer changedData) {
            this.texture = texture;
            this.x = x;
            this.y = y;
            this.z = z;
            this.width = width;
            this.height = height;
            this.depth = depth;
            this.changedData = changedData;
        }

        public ByteBuffer getChangedData() {
            return changedData;
        }

        public int getHeight() {
            return height;
        }

        public Texture getTexture() {
            return texture;
        }

        public int getWidth() {
            return width;
        }

        public int getX() {
            return x;
        }

        public int getY() {
            return y;
        }
    }

    private class ChangedVBO {

        Object3D object;
        FloatBuffer data;
        IntBuffer indices;
        int offset;

        public ChangedVBO(Object3D object, FloatBuffer data, int offset) {
            this.object = object;
            this.data = data;
            this.offset = offset;
        }

        public ChangedVBO(Object3D object, IntBuffer indices, int offset) {
            this.object = object;
            this.indices = indices;
            this.offset = offset;
        }

        public FloatBuffer getData() {
            return data;
        }

        public IntBuffer getIndices() {
            return indices;
        }

        public Object3D getObject() {
            return object;
        }

        public int getOffset() {
            return offset;
        }
    }
}
